<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
    <title>Android应用程序消息处理机制（Looper、Handler）分析 - 老罗的Android之旅
        - 博客频道 - CSDN.NET</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="description" content="Android应用程序是通过消息来驱动的，系统为每一个应用程序维护一个消息队例，应用程序的主线程不断地从这个消息队例中获取消息（Looper），然后对这些消息进行处理（Handler），这样就实现了通过消息来驱动应用程序的执行，本文将详细分析Android应用程序的消息处理机制。&#160;&#160; &#160; &#160; &#160;前面我们学习Android应用程序中的Activity启动（Android应用程序启动过程源代码分析和A" />
    <script src="http://static.blog.csdn.net/scripts/jquery.js" type="text/javascript"></script>
    <script type="text/javascript" src="http://static.blog.csdn.net/scripts/ad.js?v=1.1"></script>
        <!--new top-->
               <link rel="stylesheet" href="http://static.csdn.net/public/common/toolbar/css/index.css">        <!--new top-->

    <link rel="Stylesheet" type="text/css" href="http://static.blog.csdn.net/skin/default/css/style.css?v=1.1" />
    <link id="RSSLink" title="RSS" type="application/rss+xml" rel="alternate" href="/Luoshengyang/rss/list" />
    <link rel="shortcut icon" href="/favicon.ico" />
    <link type="text/css" rel="stylesheet" href="http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/default.css" />
 

</head>
<body>
    
   
      <!--new top-->
    <script id="toolbar-tpl-scriptId" fixed="true" prod="blog" skin="black" src="http://static.csdn.net/public/common/toolbar/js/html.js" type="text/javascript"></script>
     <!--new top-->
    <div id="container">
        <div id="header">
    <div class="header">
        <div id="blog_title">
            <h2>
                <a href="http://blog.csdn.net/luoshengyang">老罗的Android之旅</a></h2>
            <h3>爱生活，爱Android</h3>
            <div class="clear">
            </div>
        </div>
        <div class="clear">
        </div>
    </div>
</div>
<div id="navigator">
    <div class="navigator_bg">
    </div>
    <div class="navigator">
        <ul>
                <li id="btnContents"><a href="http://blog.csdn.net/luoshengyang?viewmode=contents"><span onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_mulu'])">
                    <img src="http://static.blog.csdn.net/images/ico_list.gif">目录视图</span></a></li>
                <li id="btnView"><a href="http://blog.csdn.net/luoshengyang?viewmode=list"><span onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_zhaiyao'])">
                    <img src="http://static.blog.csdn.net/images/ico_summary.gif">摘要视图</span></a></li>
                <li id="btnRss"><a href="http://blog.csdn.net/luoshengyang/rss/list"><span onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_RSS'])">
                    <img src="http://static.blog.csdn.net/images/ico_rss.gif">订阅</span></a></li>
</ul>
    </div>
</div>
<script type="text/javascript">
    var username = "Luoshengyang";
    var _blogger = username;
    var blog_address = "http://blog.csdn.net/luoshengyang";
    var static_host = "http://static.blog.csdn.net";
    var currentUserName = "";
</script>

        <div id="body">
            <div id="main">
                <div class="main">
<div class="notice tracking-ad" data-mod='popu_3' > 


<a href="http://bss.csdn.net/cview/reg/?project_id=804&identy_id=839" target="_blank"><font color=red>“程序人生”中国软件开发者职业生涯调查
</font></a>

&nbsp;&nbsp;&nbsp;&nbsp;


<a href="http://bbs.csdn.net/topics/390724670"target="_blank">
<font color=red>CSDN社区“三八节”特别活动</font></a>
&nbsp;&nbsp;&nbsp;&nbsp;


<a href="http://www.csdn.net/article/2014-03-06/2818653"target="_blank">
<font color=blue>开发者职业生涯调查之未来</font></a>




</div>                    <link href="http://static.blog.csdn.net/css/comment1.css" type="text/css" rel="stylesheet" />
<link href="http://static.blog.csdn.net/css/style1.css" type="text/css" rel="stylesheet" />
<div id="article_details" class="details">
    <div class="article_title">
    <span class="ico ico_type_Original"></span>
    <h1>
        <span class="link_title"><a href="/luoshengyang/article/details/6817933">
        Android应用程序消息处理机制（Looper、Handler）分析
        </a></span>
    </h1>
</div>

    <div class="article_manage">
        <span class="link_categories">
        分类：
            <a href="/Luoshengyang/article/category/838604">Android</a> 
        </span>
    <span class="link_postdate">2011-09-29 00:58</span>
    <span class="link_view" title="阅读次数">43837人阅读</span>
    <span class="link_comments" title="评论次数"><a href="#comments" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_pinglun'])">评论</a>(84)</span>
    <span class="link_collect"><a href="javascript:void(0);" onclick="javascript:_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_shoucang']);collectArticle('Android应用程序消息处理机制（Looper、Handler）分析','6817933');return false;" title="收藏">收藏</a></span>
    <span class="link_report"><a href="#report"  onclick="javascript:_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_jubao']);report(6817933,2);return false;" title="举报">举报</a></span>
    
</div>
<div class="tag2box"><a href='http://www.csdn.net/tag/android' target=_blank onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">android</a><a href='http://www.csdn.net/tag/class' target=_blank onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">class</a><a href='http://www.csdn.net/tag/jni' target=_blank onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">jni</a><a href='http://www.csdn.net/tag/null' target=_blank onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">null</a><a href='http://www.csdn.net/tag/%e4%bb%a3%e7%a0%81%e5%88%86%e6%9e%90' target=_blank onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">代码分析</a></div>

    
<div id="article_content" class="article_content">
<p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;Android应用程序是通过消息来驱动的，系统为每一个应用程序维护一个消息队例，应用程序的主线程不断地从这个消息队例中获取消息（Looper），然后对这些消息进行处理（Handler），这样就实现了通过消息来驱动应用程序的执行，本文将详细分析Android应用程序的消息处理机制。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;前面我们学习Android应用程序中的Activity启动（<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6689748">Android应用程序启动过程源代码分析</a>和<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6703247">Android应用程序内部启动Activity过程（startActivity）的源代码分析</a>）、Service启动（<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6677029">Android系统在新进程中启动自定义服务过程（startService）的原理分析</a>和<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6745181">Android应用程序绑定服务（bindService）的过程源代码分析</a>）以及广播发送（<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6744448">Android应用程序发送广播（sendBroadcast）的过程分析</a>）时，它们都有一个共同的特点，当ActivityManagerService需要与应用程序进行并互时，如加载Activity和Service、处理广播待，会通过<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6618363">Binder进程间通信机制</a>来知会应用程序，应用程序接收到这个请求时，它不是马上就处理这个请求，而是将这个请求封装成一个消息，然后把这个消息放在应用程序的消息队列中去，然后再通过消息循环来处理这个消息。这样做的好处就是消息的发送方只要把消息发送到应用程序的消息队列中去就行了，它可以马上返回去处理别的事情，而不需要等待消息的接收方去处理完这个消息才返回，这样就可以提高系统的并发性。实质上，这就是一种异步处理机制。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这样说可能还是比较笼统，我们以<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6689748">Android应用程序启动过程源代码分析</a>一文中所介绍的应用程序启动过程的一个片断来具体看看是如何这种消息处理机制的。在这篇文章中，要启动的应用程序称为Activity，它的默认Activity是MainActivity，它是由<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6767736">Launcher</a>来负责启动的，而Launcher又是通过ActivityManagerService来启动的，当ActivityManagerService为这个即将要启的应用程序准备好新的进程后，便通过一个<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6618363">Binder进程间通信过程</a>来通知这个新的进程来加载MainActivity，如下图所示：</p><p align="left"><img src="http://hi.csdn.net/attachment/201109/25/0_13169281559alz.gif" alt="" /><br /></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;它对应Android应用程序启动过程中的Step 30到Step 35，有兴趣的读者可以回过头去参考<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6689748">Android应用程序启动过程源代码分析</a>一文。这里的Step 30中的scheduleLaunchActivity是ActivityManagerService通过<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6618363">Binder进程间通信机制</a>发送过来的请求，它请求应用程序中的ActivityThread执行Step 34中的performLaunchActivity操作，即启动MainActivity的操作。这里我们就可以看到，Step 30的这个请求并没有等待Step 34这个操作完成就返回了，它只是把这个请求封装成一个消息，然后通过Step 31中的queueOrSendMessage操作把这个消息放到应用程序的消息队列中，然后就返回了。应用程序发现消息队列中有消息时，就会通过Step 32中的handleMessage操作来处理这个消息，即调用Step 33中的handleLaunchActivity来执行实际的加载MainAcitivy类的操作。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;了解Android应用程序的消息处理过程之后，我们就开始分样它的实现原理了。与Windows应用程序的消息处理过程一样，Android应用程序的消息处理机制也是由消息循环、消息发送和消息处理这三个部分组成的，接下来，我们就详细描述这三个过程。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;1. 消息循环</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;在消息处理机制中，消息都是存放在一个消息队列中去，而应用程序的主线程就是围绕这个消息队列进入一个无限循环的，直到应用程序退出。如果队列中有消息，应用程序的主线程就会把它取出来，并分发给相应的Handler进行处理；如果队列中没有消息，应用程序的主线程就会进入空闲等待状态，等待下一个消息的到来。在Android应用程序中，这个消息循环过程是由Looper类来实现的，它定义在frameworks/base/core/java/android/os/Looper.java文件中，在分析这个类之前，我们先看一下Android应用程序主线程是如何进入到这个消息循环中去的。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;在<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6747696">Android应用程序进程启动过程的源代码分析</a>一文中，我们分析了Android应用程序进程的启动过程，Android应用程序进程在启动的时候，会在进程中加载ActivityThread类，并且执行这个类的main函数，应用程序的消息循环过程就是在这个main函数里面实现的，我们来看看这个函数的实现，它定义在frameworks/base/core/java/android/app/ActivityThread.java文件中：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_1_186940"  code_snippet_id="130108" snippet_file_name="blog_20131227_1_186940" name="code" class="java">public final class ActivityThread {
	......

	public static final void main(String[] args) {
		......

		Looper.prepareMainLooper();

		......

		ActivityThread thread = new ActivityThread();
		thread.attach(false);
		
		......

		Looper.loop();

		......

		thread.detach();

		......
	}
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这个函数做了两件事情，一是在主线程中创建了一个ActivityThread实例，二是通过Looper类使主线程进入消息循环中，这里我们只关注后者。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;首先看Looper.prepareMainLooper函数的实现，这是一个静态成员函数，定义在frameworks/base/core/java/android/os/Looper.java文件中：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_2_9390552"  code_snippet_id="130108" snippet_file_name="blog_20131227_2_9390552" name="code" class="java">public class Looper {
	......

	private static final ThreadLocal sThreadLocal = new ThreadLocal();

	final MessageQueue mQueue;

	......

	/** Initialize the current thread as a looper.
	* This gives you a chance to create handlers that then reference
	* this looper, before actually starting the loop. Be sure to call
	* {@link #loop()} after calling this method, and end it by calling
	* {@link #quit()}.
	*/
	public static final void prepare() {
		if (sThreadLocal.get() != null) {
			throw new RuntimeException(&quot;Only one Looper may be created per thread&quot;);
		}
		sThreadLocal.set(new Looper());
	}

	/** Initialize the current thread as a looper, marking it as an application's main 
	*  looper. The main looper for your application is created by the Android environment,
	*  so you should never need to call this function yourself.
	* {@link #prepare()}
	*/

	public static final void prepareMainLooper() {
		prepare();
		setMainLooper(myLooper());
		if (Process.supportsProcesses()) {
			myLooper().mQueue.mQuitAllowed = false;
		}
	}

	private synchronized static void setMainLooper(Looper looper) {
		mMainLooper = looper;
	}

	/**
	* Return the Looper object associated with the current thread.  Returns
	* null if the calling thread is not associated with a Looper.
	*/
	public static final Looper myLooper() {
		return (Looper)sThreadLocal.get();
	}

	private Looper() {
		mQueue = new MessageQueue();
		mRun = true;
		mThread = Thread.currentThread();
	}

	......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;函数prepareMainLooper做的事情其实就是在线程中创建一个Looper对象，这个Looper对象是存放在sThreadLocal成员变量里面的，成员变量sThreadLocal的类型为ThreadLocal，表示这是一个线程局部变量，即保证每一个调用了prepareMainLooper函数的线程里面都有一个独立的Looper对象。在线程是创建Looper对象的工作是由prepare函数来完成的，而在创建Looper对象的时候，会同时创建一个消息队列MessageQueue，保存在Looper的成员变量mQueue中，后续消息就是存放在这个队列中去。消息队列在Android应用程序消息处理机制中最重要的组件，因此，我们看看它的创建过程，即它的构造函数的实现，实现frameworks/base/core/java/android/os/MessageQueue.java文件中：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_3_645071"  code_snippet_id="130108" snippet_file_name="blog_20131227_3_645071" name="code" class="java">public class MessageQueue {
	......

	private int mPtr; // used by native code

	private native void nativeInit();

	MessageQueue() {
		nativeInit();
	}

	......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;它的初始化工作都交给JNI方法nativeInit来实现了，这个JNI方法定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_4_1343183"  code_snippet_id="130108" snippet_file_name="blog_20131227_4_1343183" name="code" class="cpp">static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (! nativeMessageQueue) {
        jniThrowRuntimeException(env, &quot;Unable to allocate native queue&quot;);
        return;
    }

    android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;在JNI中，也相应地创建了一个消息队列NativeMessageQueue，NativeMessageQueue类也是定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中，它的创建过程如下所示：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_5_4865193"  code_snippet_id="130108" snippet_file_name="blog_20131227_5_4865193" name="code" class="cpp">NativeMessageQueue::NativeMessageQueue() {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;它主要就是在内部创建了一个Looper对象，注意，这个Looper对象是实现在JNI层的，它与上面Java层中的Looper是不一样的，不过它们是对应的，下面我们进一步分析消息循环的过程的时候，读者就会清楚地了解到它们之间的关系。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这个Looper的创建过程也很重要，不过我们暂时放一放，先分析完android_os_MessageQueue_nativeInit函数的执行，它创建了本地消息队列NativeMessageQueue对象之后，接着调用android_os_MessageQueue_setNativeMessageQueue函数来把这个消息队列对象保存在前面我们在Java层中创建的MessageQueue对象的mPtr成员变量里面：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_6_8594947"  code_snippet_id="130108" snippet_file_name="blog_20131227_6_8594947" name="code" class="cpp">static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,
        NativeMessageQueue* nativeMessageQueue) {
    env-&gt;SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr,
             reinterpret_cast&lt;jint&gt;(nativeMessageQueue));
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这里传进来的参数messageQueueObj即为我们前面在Java层创建的消息队列对象，而gMessageQueueClassInfo.mPtr即表示在Java类MessageQueue中，其成员变量mPtr的偏移量，通过这个偏移量，就可以把这个本地消息队列对象natvieMessageQueue保存在Java层创建的消息队列对象的mPtr成员变量中，这是为了后续我们调用Java层的消息队列对象的其它成员函数进入到JNI层时，能够方便地找回它在JNI层所对应的消息队列对象。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;我们再回到NativeMessageQueue的构造函数中，看看JNI层的Looper对象的创建过程，即看看它的构造函数是如何实现的，这个Looper类实现在frameworks/base/libs/utils/Looper.cpp文件中：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_7_5007680"  code_snippet_id="130108" snippet_file_name="blog_20131227_7_5007680" name="code" class="cpp">Looper::Looper(bool allowNonCallbacks) :
	mAllowNonCallbacks(allowNonCallbacks),
	mResponseIndex(0) {
	int wakeFds[2];
	int result = pipe(wakeFds);
	......

	mWakeReadPipeFd = wakeFds[0];
	mWakeWritePipeFd = wakeFds[1];

	......

#ifdef LOOPER_USES_EPOLL
	// Allocate the epoll instance and register the wake pipe.
	mEpollFd = epoll_create(EPOLL_SIZE_HINT);
	......

	struct epoll_event eventItem;
	memset(&amp; eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
	eventItem.events = EPOLLIN;
	eventItem.data.fd = mWakeReadPipeFd;
	result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &amp; eventItem);
	......
#else
	......
#endif

	......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这个构造函数做的事情非常重要，它跟我们后面要介绍的应用程序主线程在消息队列中没有消息时要进入等待状态以及当消息队列有消息时要把应用程序主线程唤醒的这两个知识点息息相关。它主要就是通过pipe系统调用来创建了一个管道了：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_8_8529690"  code_snippet_id="130108" snippet_file_name="blog_20131227_8_8529690" name="code" class="cpp">int wakeFds[2];
int result = pipe(wakeFds);
......

mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;管道是Linux系统中的一种进程间通信机制，具体可以参考前面一篇文章<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6557518">Android学习启动篇</a>推荐的一本书《Linux内核源代码情景分析》中的第6章--传统的Uinx进程间通信。简单来说，管道就是一个文件，在管道的两端，分别是两个打开文件文件描述符，这两个打开文件描述符都是对应同一个文件，其中一个是用来读的，别一个是用来写的，一般的使用方式就是，一个线程通过读文件描述符中来读管道的内容，当管道没有内容时，这个线程就会进入等待状态，而另外一个线程通过写文件描述符来向管道中写入内容，写入内容的时候，如果另一端正有线程正在等待管道中的内容，那么这个线程就会被唤醒。这个等待和唤醒的操作是如何进行的呢，这就要借助Linux系统中的epoll机制了。&nbsp;Linux系统中的epoll机制为处理大批量句柄而作了改进的poll，是Linux下多路复用IO接口select/poll的增强版本，它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。但是这里我们其实只需要监控的IO接口只有mWakeReadPipeFd一个，即前面我们所创建的管道的读端，为什么还需要用到epoll呢？有点用牛刀来杀鸡的味道。其实不然，这个Looper类是非常强大的，它除了监控内部所创建的管道接口之外，还提供了addFd接口供外界面调用，外界可以通过这个接口把自己想要监控的IO事件一并加入到这个Looper对象中去，当所有这些被监控的IO接口上面有事件发生时，就会唤醒相应的线程来处理，不过这里我们只关心刚才所创建的管道的IO事件的发生。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;要使用Linux系统的epoll机制，首先要通过epoll_create来创建一个epoll专用的文件描述符：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_9_6403902"  code_snippet_id="130108" snippet_file_name="blog_20131227_9_6403902" name="code" class="cpp">mEpollFd = epoll_create(EPOLL_SIZE_HINT);</pre>&nbsp;&nbsp; &nbsp; &nbsp; 传入的参数EPOLL_SIZE_HINT是在这个mEpollFd上能监控的最大文件描述符数。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; 接着还要通过epoll_ctl函数来告诉epoll要监控相应的文件描述符的什么事件：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_10_2434168"  code_snippet_id="130108" snippet_file_name="blog_20131227_10_2434168" name="code" class="cpp">struct epoll_event eventItem;
memset(&amp; eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeReadPipeFd;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &amp; eventItem);</pre>&nbsp;&nbsp; &nbsp; &nbsp; 这里就是告诉mEpollFd，它要监控mWakeReadPipeFd文件描述符的EPOLLIN事件，即当管道中有内容可读时，就唤醒当前正在等待管道中的内容的线程。<br />&nbsp;&nbsp; &nbsp; &nbsp; C++层的这个Looper对象创建好了之后，就返回到JNI层的NativeMessageQueue的构造函数，最后就返回到Java层的消息队列MessageQueue的创建过程，这样，Java层的Looper对象就准备好了。有点复杂，我们先小结一下这一步都做了些什么事情：<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; A. 在Java层，创建了一个Looper对象，这个Looper对象是用来进入消息循环的，它的内部有一个消息队列MessageQueue对象mQueue；</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; B. 在JNI层，创建了一个NativeMessageQueue对象，这个NativeMessageQueue对象保存在Java层的消息队列对象mQueue的成员变量mPtr中；</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; C. 在C++层，创建了一个Looper对象，保存在JNI层的NativeMessageQueue对象的成员变量mLooper中，这个对象的作用是，当Java层的消息队列中没有消息时，就使Android应用程序主线程进入等待状态，而当Java层的消息队列中来了新的消息后，就唤醒Android应用程序的主线程来处理这个消息。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; 回到ActivityThread类的main函数中，在上面这些工作都准备好之后，就调用Looper类的loop函数进入到消息循环中去了：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_11_3132280"  code_snippet_id="130108" snippet_file_name="blog_20131227_11_3132280" name="code" class="cpp">public class Looper {
	......

	public static final void loop() {
		Looper me = myLooper();
		MessageQueue queue = me.mQueue;

		......

		while (true) {
			Message msg = queue.next(); // might block
			......

			if (msg != null) {
				if (msg.target == null) {
					// No target is a magic identifier for the quit message.
					return;
				}

				......

				msg.target.dispatchMessage(msg);
				
				......

				msg.recycle();
			}
		}
	}

	......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这里就是进入到消息循环中去了，它不断地从消息队列mQueue中去获取下一个要处理的消息msg，如果消息的target成员变量为null，就表示要退出消息循环了，否则的话就要调用这个target对象的dispatchMessage成员函数来处理这个消息，这个target对象的类型为Handler，下面我们分析消息的发送时会看到这个消息对象msg是如设置的。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这个函数最关键的地方便是从消息队列中获取下一个要处理的消息了，即MessageQueue.next函数，它实现frameworks/base/core/java/android/os/MessageQueue.java文件中：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_12_3830391"  code_snippet_id="130108" snippet_file_name="blog_20131227_12_3830391" name="code" class="java">public class MessageQueue {
	......

	final Message next() {
		int pendingIdleHandlerCount = -1; // -1 only during first iteration
		int nextPollTimeoutMillis = 0;

		for (;;) {
			if (nextPollTimeoutMillis != 0) {
				Binder.flushPendingCommands();
			}
			nativePollOnce(mPtr, nextPollTimeoutMillis);

			synchronized (this) {
				// Try to retrieve the next message.  Return if found.
				final long now = SystemClock.uptimeMillis();
				final Message msg = mMessages;
				if (msg != null) {
					final long when = msg.when;
					if (now &gt;= when) {
						mBlocked = false;
						mMessages = msg.next;
						msg.next = null;
						if (Config.LOGV) Log.v(&quot;MessageQueue&quot;, &quot;Returning message: &quot; + msg);
						return msg;
					} else {
						nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
					}
				} else {
					nextPollTimeoutMillis = -1;
				}

				// If first time, then get the number of idlers to run.
				if (pendingIdleHandlerCount &lt; 0) {
					pendingIdleHandlerCount = mIdleHandlers.size();
				}
				if (pendingIdleHandlerCount == 0) {
					// No idle handlers to run.  Loop and wait some more.
					mBlocked = true;
					continue;
				}

				if (mPendingIdleHandlers == null) {
					mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
				}
				mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
			}

			// Run the idle handlers.
			// We only ever reach this code block during the first iteration.
			for (int i = 0; i &lt; pendingIdleHandlerCount; i++) {
				final IdleHandler idler = mPendingIdleHandlers[i];
				mPendingIdleHandlers[i] = null; // release the reference to the handler

				boolean keep = false;
				try {
					keep = idler.queueIdle();
				} catch (Throwable t) {
					Log.wtf(&quot;MessageQueue&quot;, &quot;IdleHandler threw exception&quot;, t);
				}

				if (!keep) {
					synchronized (this) {
						mIdleHandlers.remove(idler);
					}
				}
			}

			// Reset the idle handler count to 0 so we do not run them again.
			pendingIdleHandlerCount = 0;

			// While calling an idle handler, a new message could have been delivered
			// so go back and look again for a pending message without waiting.
			nextPollTimeoutMillis = 0;
		}
	}

	......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;调用这个函数的时候，有可能会让线程进入等待状态。什么情况下，线程会进入等待状态呢？两种情况，一是当消息队列中没有消息时，它会使线程进入等待状态；二是消息队列中有消息，但是消息指定了执行的时间，而现在还没有到这个时间，线程也会进入等待状态。消息队列中的消息是按时间先后来排序的，后面我们在分析消息的发送时会看到。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;执行下面语句是看看当前消息队列中有没有消息：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_13_5400554"  code_snippet_id="130108" snippet_file_name="blog_20131227_13_5400554" name="code" class="java">nativePollOnce(mPtr, nextPollTimeoutMillis);</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这是一个JNI方法，我们等一下再分析，这里传入的参数mPtr就是指向前面我们在JNI层创建的NativeMessageQueue对象了，而参数nextPollTimeoutMillis则表示如果当前消息队列中没有消息，它要等待的时候，for循环开始时，传入的值为0，表示不等待。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;当前nativePollOnce返回后，就去看看消息队列中有没有消息：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_14_5750001"  code_snippet_id="130108" snippet_file_name="blog_20131227_14_5750001" name="code" class="java">final Message msg = mMessages;
if (msg != null) {
	final long when = msg.when;
	if (now &gt;= when) {
		mBlocked = false;
		mMessages = msg.next;
		msg.next = null;
		if (Config.LOGV) Log.v(&quot;MessageQueue&quot;, &quot;Returning message: &quot; + msg);
		return msg;
	} else {
		nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
	}
} else {
	nextPollTimeoutMillis = -1;
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;如果消息队列中有消息，并且当前时候大于等于消息中的执行时间，那么就直接返回这个消息给Looper.loop消息处理，否则的话就要等待到消息的执行时间：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_15_6971500"  code_snippet_id="130108" snippet_file_name="blog_20131227_15_6971500" name="code" class="java">nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;如果消息队列中没有消息，那就要进入无穷等待状态直到有新消息了：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_16_2653102"  code_snippet_id="130108" snippet_file_name="blog_20131227_16_2653102" name="code" class="java">nextPollTimeoutMillis = -1;</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;-1表示下次调用nativePollOnce时，如果消息中没有消息，就进入无限等待状态中去。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这里计算出来的等待时间都是在下次调用nativePollOnce时使用的。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这里说的等待，是空闲等待，而不是忙等待，因此，在进入空闲等待状态前，如果应用程序注册了IdleHandler接口来处理一些事情，那么就会先执行这里IdleHandler，然后再进入等待状态。IdlerHandler是定义在MessageQueue的一个内部类：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_17_4223266"  code_snippet_id="130108" snippet_file_name="blog_20131227_17_4223266" name="code" class="java">public class MessageQueue {
	......

	/**
	* Callback interface for discovering when a thread is going to block
	* waiting for more messages.
	*/
	public static interface IdleHandler {
		/**
		* Called when the message queue has run out of messages and will now
		* wait for more.  Return true to keep your idle handler active, false
		* to have it removed.  This may be called if there are still messages
		* pending in the queue, but they are all scheduled to be dispatched
		* after the current time.
		*/
		boolean queueIdle();
	}

	......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;它只有一个成员函数queueIdle，执行这个函数时，如果返回值为false，那么就会从应用程序中移除这个IdleHandler，否则的话就会在应用程序中继续维护着这个IdleHandler，下次空闲时仍会再执会这个IdleHandler。MessageQueue提供了addIdleHandler和removeIdleHandler两注册和删除IdleHandler。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;回到MessageQueue函数中，它接下来就是在进入等待状态前，看看有没有IdleHandler是需要执行的：<br /></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_18_1017682"  code_snippet_id="130108" snippet_file_name="blog_20131227_18_1017682" name="code" class="java">// If first time, then get the number of idlers to run.
if (pendingIdleHandlerCount &lt; 0) {
	pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount == 0) {
	// No idle handlers to run.  Loop and wait some more.
	mBlocked = true;
	continue;
}

if (mPendingIdleHandlers == null) {
	mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;如果没有，即pendingIdleHandlerCount等于0，那下面的逻辑就不执行了，通过continue语句直接进入下一次循环，否则就要把注册在mIdleHandlers中的IdleHandler取出来，放在mPendingIdleHandlers数组中去。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;接下来就是执行这些注册了的IdleHanlder了：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_19_4539693"  code_snippet_id="130108" snippet_file_name="blog_20131227_19_4539693" name="code" class="java">// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i &lt; pendingIdleHandlerCount; i++) {
      final IdleHandler idler = mPendingIdleHandlers[i];
      mPendingIdleHandlers[i] = null; // release the reference to the handler

      boolean keep = false;
      try {
            keep = idler.queueIdle();
      } catch (Throwable t) {
            Log.wtf(&quot;MessageQueue&quot;, &quot;IdleHandler threw exception&quot;, t);
      }

      if (!keep) {
            synchronized (this) {
                    mIdleHandlers.remove(idler);
            }
      }
}
</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; 执行完这些IdleHandler之后，线程下次调用nativePollOnce函数时，就不设置超时时间了，因为，很有可能在执行IdleHandler的时候，已经有新的消息加入到消息队列中去了，因此，要重置nextPollTimeoutMillis的值：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_20_1649754"  code_snippet_id="130108" snippet_file_name="blog_20131227_20_1649754" name="code" class="java">// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;分析完MessageQueue的这个next函数之后，我们就要深入分析一下JNI方法nativePollOnce了，看看它是如何进入等待状态的，这个函数定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中：<br /><p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_21_396018"  code_snippet_id="130108" snippet_file_name="blog_20131227_21_396018" name="code" class="cpp">static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jint ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast&lt;NativeMessageQueue*&gt;(ptr);
    nativeMessageQueue-&gt;pollOnce(timeoutMillis);
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这个函数首先是通过传进入的参数ptr取回前面在Java层创建MessageQueue对象时在JNI层创建的NatvieMessageQueue对象，然后调用它的pollOnce函数：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_22_8585874"  code_snippet_id="130108" snippet_file_name="blog_20131227_22_8585874" name="code" class="cpp">void NativeMessageQueue::pollOnce(int timeoutMillis) {
    mLooper-&gt;pollOnce(timeoutMillis);
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这里将操作转发给mLooper对象的pollOnce函数处理，这里的mLooper对象是在C++层的对象，它也是在前面在JNI层创建的NatvieMessageQueue对象时创建的，它的pollOnce函数定义在frameworks/base/libs/utils/Looper.cpp文件中：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_23_8727578"  code_snippet_id="130108" snippet_file_name="blog_20131227_23_8727578" name="code" class="cpp">int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
	int result = 0;
	for (;;) {
		......

		if (result != 0) {
			......

			return result;
		}

		result = pollInner(timeoutMillis);
	}
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;为了方便讨论，我们把这个函数的无关部分都去掉，它主要就是调用pollInner函数来进一步操作，如果pollInner返回值不等于0，这个函数就可以返回了。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;函数pollInner的定义如下：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_24_7473842"  code_snippet_id="130108" snippet_file_name="blog_20131227_24_7473842" name="code" class="cpp">int Looper::pollInner(int timeoutMillis) {
	......

	int result = ALOOPER_POLL_WAKE;

	......

#ifdef LOOPER_USES_EPOLL
	struct epoll_event eventItems[EPOLL_MAX_EVENTS];
	int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
	bool acquiredLock = false;
#else
	......
#endif

	if (eventCount &lt; 0) {
		if (errno == EINTR) {
			goto Done;
		}

		LOGW(&quot;Poll failed with an unexpected error, errno=%d&quot;, errno);
		result = ALOOPER_POLL_ERROR;
		goto Done;
	}

	if (eventCount == 0) {
		......
		result = ALOOPER_POLL_TIMEOUT;
		goto Done;
	}

	......

#ifdef LOOPER_USES_EPOLL
	for (int i = 0; i &lt; eventCount; i++) {
		int fd = eventItems[i].data.fd;
		uint32_t epollEvents = eventItems[i].events;
		if (fd == mWakeReadPipeFd) {
			if (epollEvents &amp; EPOLLIN) {
				awoken();
			} else {
				LOGW(&quot;Ignoring unexpected epoll events 0x%x on wake read pipe.&quot;, epollEvents);
			}
		} else {
			......
		}
	}
	if (acquiredLock) {
		mLock.unlock();
	}
Done: ;
#else
	......
#endif

	......

	return result;
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这里，首先是调用epoll_wait函数来看看epoll专用文件描述符mEpollFd所监控的文件描述符是否有IO事件发生，它设置监控的超时时间为timeoutMillis：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_25_8171953"  code_snippet_id="130108" snippet_file_name="blog_20131227_25_8171953" name="code" class="cpp">int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;回忆一下前面的Looper的构造函数，我们在里面设置了要监控mWakeReadPipeFd文件描述符的EPOLLIN事件。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;当mEpollFd所监控的文件描述符发生了要监控的IO事件后或者监控时间超时后，线程就从epoll_wait返回了，否则线程就会在epoll_wait函数中进入睡眠状态了。返回后如果eventCount等于0，就说明是超时了：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_26_473248"  code_snippet_id="130108" snippet_file_name="blog_20131227_26_473248" name="code" class="cpp">if (eventCount == 0) {
    ......
    result = ALOOPER_POLL_TIMEOUT;
    goto Done;
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; 如果eventCount不等于0，就说明发生要监控的事件：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_27_5598442"  code_snippet_id="130108" snippet_file_name="blog_20131227_27_5598442" name="code" class="cpp">for (int i = 0; i &lt; eventCount; i++) {
	int fd = eventItems[i].data.fd;
	uint32_t epollEvents = eventItems[i].events;
	if (fd == mWakeReadPipeFd) {
		if (epollEvents &amp; EPOLLIN) {
			awoken();
		} else {
			LOGW(&quot;Ignoring unexpected epoll events 0x%x on wake read pipe.&quot;, epollEvents);
		}
	} else {
			......
	}
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这里我们只关注mWakeReadPipeFd文件描述符上的事件，如果在mWakeReadPipeFd文件描述符上发生了EPOLLIN就说明应用程序中的消息队列里面有新的消息需要处理了，接下来它就会先调用awoken函数清空管道中把内容，以便下次再调用pollInner函数时，知道自从上次处理完消息队列中的消息后，有没有新的消息加进来。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;函数awoken的实现很简单，它只是把管道中的内容都读取出来：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_28_756656"  code_snippet_id="130108" snippet_file_name="blog_20131227_28_756656" name="code" class="cpp">void Looper::awoken() {
	......

	char buffer[16];
	ssize_t nRead;
	do {
		nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
	} while ((nRead == -1 &amp;&amp; errno == EINTR) || nRead == sizeof(buffer));
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;因为当其它的线程向应用程序的消息队列加入新的消息时，会向这个管道写入新的内容来通知应用程序主线程有新的消息需要处理了，下面我们分析消息的发送的时候将会看到。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这样，消息的循环过程就分析完了，这部分逻辑还是比较复杂的，它利用Linux系统中的管道（pipe）进程间通信机制来实现消息的等待和处理，不过，了解了这部分内容之后，下面我们分析消息的发送和处理就简单多了。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;2. 消息的发送<br />&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;应用程序的主线程准备就好消息队列并且进入到消息循环后，其它地方就可以往这个消息队列中发送消息了。我们继续以文章开始介绍的<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6689748">Android应用程序启动过程源代码分析</a>一文中的应用程序启动过为例，说明应用程序是如何把消息加入到应用程序的消息队列中去的。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;在<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6689748">Android应用程序启动过程源代码分析</a>这篇文章的Step 30中，ActivityManagerService通过调用ApplicationThread类的scheduleLaunchActivity函数通知应用程序，它可以加载应用程序的默认Activity了，这个函数定义在frameworks/base/core/java/android/app/ActivityThread.java文件中：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_29_5009797"  code_snippet_id="130108" snippet_file_name="blog_20131227_29_5009797" name="code" class="java">public final class ActivityThread {  
  
    ......  
  
    private final class ApplicationThread extends ApplicationThreadNative {  
  
        ......  
  
        // we use token to identify this activity without having to send the  
        // activity itself back to the activity manager. (matters more with ipc)  
        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,  
                ActivityInfo info, Bundle state, List&lt;ResultInfo&gt; pendingResults,  
                List&lt;Intent&gt; pendingNewIntents, boolean notResumed, boolean isForward) {  
            ActivityClientRecord r = new ActivityClientRecord();  
  
            r.token = token;  
            r.ident = ident;  
            r.intent = intent;  
            r.activityInfo = info;  
            r.state = state;  
  
            r.pendingResults = pendingResults;  
            r.pendingIntents = pendingNewIntents;  
  
            r.startsNotResumed = notResumed;  
            r.isForward = isForward;  
  
            queueOrSendMessage(H.LAUNCH_ACTIVITY, r);  
        }  
  
        ......  
  
    }  
  
    ......  
}  </pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这里把相关的参数都封装成一个ActivityClientRecord对象r，然后调用queueOrSendMessage函数来往应用程序的消息队列中加入一个新的消息（H.LAUNCH_ACTIVITY），这个函数定义在frameworks/base/core/java/android/app/ActivityThread.java文件中：<br /><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_30_6787704"  code_snippet_id="130108" snippet_file_name="blog_20131227_30_6787704" name="code" class="java">public final class ActivityThread {  
  
    ......  
  
    private final class ApplicationThread extends ApplicationThreadNative {  
  
        ......  
  
        // if the thread hasn't started yet, we don't have the handler, so just  
        // save the messages until we're ready.  
        private final void queueOrSendMessage(int what, Object obj) {  
            queueOrSendMessage(what, obj, 0, 0);  
        }  
  
        ......  
  
        private final void queueOrSendMessage(int what, Object obj, int arg1, int arg2) {  
            synchronized (this) {  
                ......  
                Message msg = Message.obtain();  
                msg.what = what;  
                msg.obj = obj;  
                msg.arg1 = arg1;  
                msg.arg2 = arg2;  
                mH.sendMessage(msg);  
            }  
        }  
  
        ......  
  
    }  
  
    ......  
}  </pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;在queueOrSendMessage函数中，又进一步把上面传进来的参数封装成一个Message对象msg，然后通过mH.sendMessage函数把这个消息对象msg加入到应用程序的消息队列中去。这里的mH是ActivityThread类的成员变量，它的类型为H，继承于Handler类，它定义在frameworks/base/core/java/android/app/ActivityThread.java文件中：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_31_9437662"  code_snippet_id="130108" snippet_file_name="blog_20131227_31_9437662" name="code" class="java">public final class ActivityThread {  
  
    ......  
  
    private final class H extends Handler {  
  
        ......  
  
        public void handleMessage(Message msg) {  
            ......  
            switch (msg.what) {    
            ......  
            }  
  
        ......  
  
    }  
  
    ......  
} </pre><p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这个H类就是通过其成员函数handleMessage函数来处理消息的了，后面我们分析消息的处理过程时会看到。<br />&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;ActivityThread类的这个mH成员变量是什么时候创建的呢？我们前面在分析应用程序的消息循环时，说到当应用程序进程启动之后，就会加载ActivityThread类的main函数里面，在这个main函数里面，在通过Looper类进入消息循环之前，会在当前进程中创建一个ActivityThread实例：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_32_6199060"  code_snippet_id="130108" snippet_file_name="blog_20131227_32_6199060" name="code" class="java">public final class ActivityThread {
	......

	public static final void main(String[] args) {
		......

		ActivityThread thread = new ActivityThread();
		thread.attach(false);

		......
	}
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;在创建这个实例的时候，就会同时创建其成员变量mH了：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_33_2752713"  code_snippet_id="130108" snippet_file_name="blog_20131227_33_2752713" name="code" class="java">public final class ActivityThread {
	......

	final H mH = new H();

	......
} </pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;前面说过，H类继承于Handler类，因此，当创建这个H对象时，会调用Handler类的构造函数，这个函数定义在frameworks/base/core/java/android/os/Handler.java文件中：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_34_5402672"  code_snippet_id="130108" snippet_file_name="blog_20131227_34_5402672" name="code" class="java">public class Handler {
	......

	public Handler() {
		......

		mLooper = Looper.myLooper();
		......

		mQueue = mLooper.mQueue;
		......
	}


	final MessageQueue mQueue;
	final Looper mLooper;
	......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;在Hanlder类的构造函数中，主要就是初始成员变量mLooper和mQueue了。这里的myLooper是Looper类的静态成员函数，通过它来获得一个Looper对象，这个Looper对象就是前面我们在分析消息循环时，在ActivityThread类的main函数中通过Looper.prepareMainLooper函数创建的。Looper.myLooper函数实现在frameworks/base/core/java/android/os/Looper.java文件中：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_35_1292017"  code_snippet_id="130108" snippet_file_name="blog_20131227_35_1292017" name="code" class="java">public class Looper {
	......

	public static final Looper myLooper() {
		return (Looper)sThreadLocal.get();
	}

	......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;有了这个Looper对象后，就可以通过Looper.mQueue来访问应用程序的消息队列了。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;有了这个Handler对象mH后，就可以通过它来往应用程序的消息队列中加入新的消息了。回到前面的queueOrSendMessage函数中，当它准备好了一个Message对象msg后，就开始调用mH.sendMessage函数来发送消息了，这个函数定义在frameworks/base/core/java/android/os/Handler.java文件中：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_36_2862180"  code_snippet_id="130108" snippet_file_name="blog_20131227_36_2862180" name="code" class="java">public class Handler {
	......

	public final boolean sendMessage(Message msg)
	{
		return sendMessageDelayed(msg, 0);
	}

	public final boolean sendMessageDelayed(Message msg, long delayMillis)
	{
		if (delayMillis &lt; 0) {
			delayMillis = 0;
		}
		return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
	}

	public boolean sendMessageAtTime(Message msg, long uptimeMillis)
	{
		boolean sent = false;
		MessageQueue queue = mQueue;
		if (queue != null) {
			msg.target = this;
			sent = queue.enqueueMessage(msg, uptimeMillis);
		}
		else {
			......
		}
		return sent;
	}

	......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;在发送消息时，是可以指定消息的处理时间的，但是通过sendMessage函数发送的消息的处理时间默认就为当前时间，即表示要马上处理，因此，从sendMessage函数中调用sendMessageDelayed函数，传入的时间参数为0，表示这个消息不要延时处理，而在sendMessageDelayed函数中，则会先获得当前时间，然后加上消息要延时处理的时间，即得到这个处理这个消息的绝对时间，然后调用sendMessageAtTime函数来把消息加入到应用程序的消息队列中去。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;在sendMessageAtTime函数，首先得到应用程序的消息队列mQueue，这是在Handler对象构造时初始化好的，前面已经分析过了，接着设置这个消息的目标对象target，即这个消息最终是由谁来处理的：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_37_6035527"  code_snippet_id="130108" snippet_file_name="blog_20131227_37_6035527" name="code" class="java">msg.target = this;</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这里将它赋值为this，即表示这个消息最终由这个Handler对象来处理，即由ActivityThread对象的mH成员变量来处理。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;函数最后调用queue.enqueueMessage来把这个消息加入到应用程序的消息队列中去，这个函数实现在frameworks/base/core/java/android/os/MessageQueue.java文件中：<br /></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_38_5861586"  code_snippet_id="130108" snippet_file_name="blog_20131227_38_5861586" name="code" class="java">public class MessageQueue {
	......

	final boolean enqueueMessage(Message msg, long when) {
		......

		final boolean needWake;
		synchronized (this) {
			......

			msg.when = when;
			//Log.d(&quot;MessageQueue&quot;, &quot;Enqueing: &quot; + msg);
			Message p = mMessages;
			if (p == null || when == 0 || when &lt; p.when) {
				msg.next = p;
				mMessages = msg;
				needWake = mBlocked; // new head, might need to wake up
			} else {
				Message prev = null;
				while (p != null &amp;&amp; p.when &lt;= when) {
					prev = p;
					p = p.next;
				}
				msg.next = prev.next;
				prev.next = msg;
				needWake = false; // still waiting on head, no need to wake up
			}

		}
		if (needWake) {
			nativeWake(mPtr);
		}
		return true;
	}

	......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;把消息加入到消息队列时，分两种情况，一种当前消息队列为空时，这时候应用程序的主线程一般就是处于空闲等待状态了，这时候就要唤醒它，另一种情况是应用程序的消息队列不为空，这时候就不需要唤醒应用程序的主线程了，因为这时候它一定是在忙着处于消息队列中的消息，因此不会处于空闲等待的状态。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;第一种情况比较简单，只要把消息放在消息队列头就可以了：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_39_7083085"  code_snippet_id="130108" snippet_file_name="blog_20131227_39_7083085" name="code" class="java">msg.next = p;
mMessages = msg;
needWake = mBlocked; // new head, might need to wake up</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;第二种情况相对就比较复杂一些了，前面我们说过，当往消息队列中发送消息时，是可以指定消息的处理时间的，而消息队列中的消息，就是按照这个时间从小到大来排序的，因此，当把新的消息加入到消息队列时，就要根据它的处理时间来找到合适的位置，然后再放进消息队列中去：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_40_8304584"  code_snippet_id="130108" snippet_file_name="blog_20131227_40_8304584" name="code" class="java">Message prev = null;
while (p != null &amp;&amp; p.when &lt;= when) {
	prev = p;
	p = p.next;
}
msg.next = prev.next;
prev.next = msg;
needWake = false; // still waiting on head, no need to wake up</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;把消息加入到消息队列去后，如果应用程序的主线程正处于空闲等待状态，就需要调用natvieWake函数来唤醒它了，这是一个JNI方法，定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_41_3462798"  code_snippet_id="130108" snippet_file_name="blog_20131227_41_3462798" name="code" class="java">static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj, jint ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast&lt;NativeMessageQueue*&gt;(ptr);
    return nativeMessageQueue-&gt;wake();
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这个JNI层的NativeMessageQueue对象我们在前面分析消息循环的时候创建好的，保存在Java层的MessageQueue对象的mPtr成员变量中，这里把它取回来之后，就调用它的wake函数来唤醒应用程序的主线程，这个函数也是定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_42_8064604"  code_snippet_id="130108" snippet_file_name="blog_20131227_42_8064604" name="code" class="java">void NativeMessageQueue::wake() {
    mLooper-&gt;wake();
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这里它又通过成员变量mLooper的wake函数来执行操作，这里的mLooper成员变量是一个C++层实现的Looper对象，它定义在frameworks/base/libs/utils/Looper.cpp文件中：<br /><p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_43_4826001"  code_snippet_id="130108" snippet_file_name="blog_20131227_43_4826001" name="code" class="java">void Looper::wake() {
	......

	ssize_t nWrite;
	do {
		nWrite = write(mWakeWritePipeFd, &quot;W&quot;, 1);
	} while (nWrite == -1 &amp;&amp; errno == EINTR);

	.......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这个wake函数很简单，只是通过打开文件描述符mWakeWritePipeFd往管道的写入一个&quot;W&quot;字符串。其实，往管道写入什么内容并不重要，往管道写入内容的目的是为了唤醒应用程序的主线程。前面我们在分析应用程序的消息循环时说到，当应用程序的消息队列中没有消息处理时，应用程序的主线程就会进入空闲等待状态，而这个空闲等待状态就是通过调用这个Looper类的pollInner函数来进入的，具体就是在pollInner函数中调用epoll_wait函数来等待管道中有内容可读的。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这时候既然管道中有内容可读了，应用程序的主线程就会从这里的Looper类的pollInner函数返回到JNI层的nativePollOnce函数，最后返回到Java层中的MessageQueue.next函数中去，这里它就会发现消息队列中有新的消息需要处理了，于就会处理这个消息。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;3. 消息的处理</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;前面在分析消息循环时，说到应用程序的主线程是在Looper类的loop成员函数中进行消息循环过程的，这个函数定义在frameworks/base/core/java/android/os/Looper.java文件中：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_44_4095653"  code_snippet_id="130108" snippet_file_name="blog_20131227_44_4095653" name="code" class="java">public class Looper {
	......

	public static final void loop() {
		Looper me = myLooper();
		MessageQueue queue = me.mQueue;

		......

		while (true) {
			Message msg = queue.next(); // might block
			......

			if (msg != null) {
				if (msg.target == null) {
					// No target is a magic identifier for the quit message.
					return;
				}

				......

				msg.target.dispatchMessage(msg);
				
				......

				msg.recycle();
			}
		}
	}

	......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;它从消息队列中获得消息对象msg后，就会调用它的target成员变量的dispatchMessage函数来处理这个消息。在前面分析消息的发送时说过，这个消息对象msg的成员变量target是在发送消息的时候设置好的，一般就通过哪个Handler来发送消息，就通过哪个Handler来处理消息。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;我们继续以前面分析消息的发送时所举的例子来分析消息的处理过程。前面说到，在<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6689748">Android应用程序启动过程源代码分析</a>这篇文章的Step 30中，ActivityManagerService通过调用ApplicationThread类的scheduleLaunchActivity函数通知应用程序，它可以加载应用程序的默认Activity了，而ApplicationThread类的scheduleLaunchActivity函数最终把这个请求封装成一个消息，然后通过ActivityThread类的成员变量mH来把这个消息加入到应用程序的消息队列中去。现在要对这个消息进行处理了，于是就会调用H类的dispatchMessage函数进行处理。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;H类没有实现自己的dispatchMessage函数，但是它继承了父类Handler的dispatchMessage函数，这个函数定义在frameworks/base/core/java/android/os/ Handler.java文件中：</p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_45_1205714"  code_snippet_id="130108" snippet_file_name="blog_20131227_45_1205714" name="code" class="java">public class Handler {
	......

	public void dispatchMessage(Message msg) {
		if (msg.callback != null) {
			handleCallback(msg);
		} else {
			if (mCallback != null) {
				if (mCallback.handleMessage(msg)) {
					return;
				}
			}
			handleMessage(msg);
		}
	}

	......
}</pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;这里的消息对象msg的callback成员变量和Handler类的mCallBack成员变量一般都为null，于是，就会调用Handler类的handleMessage函数来处理这个消息，由于H类在继承Handler类时，重写了handleMessage函数，因此，这里调用的实际上是H类的handleMessage函数，这个函数定义在frameworks/base/core/java/android/app/ActivityThread.java文件中：<p></p><p align="left"></p><pre code_snippet_id="130108" snippet_file_name="blog_20131227_46_5491875"  code_snippet_id="130108" snippet_file_name="blog_20131227_46_5491875" name="code" class="java">public final class ActivityThread {  
  
    ......  
  
    private final class H extends Handler {  
  
        ......  
  
        public void handleMessage(Message msg) {  
            ......  
            switch (msg.what) {  
            case LAUNCH_ACTIVITY: {  
                ActivityClientRecord r = (ActivityClientRecord)msg.obj;  
  
                r.packageInfo = getPackageInfoNoCheck(  
                    r.activityInfo.applicationInfo);  
                handleLaunchActivity(r, null);  
            } break;  
            ......  
            }  
  
        ......  
  
    }  
  
    ......  
}  </pre>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; 因为前面在分析消息的发送时所举的例子中，发送的消息的类型为H.LAUNCH_ACTIVITY，因此，这里就会调用ActivityThread类的handleLaunchActivity函数来真正地处理这个消息了，后面的具体过程就可以参考<a target=_blank target="_blank" href="http://blog.csdn.net/luoshengyang/article/details/6689748">Android应用程序启动过程源代码分析</a>这篇文章了。<p></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; 至此，我们就从消息循环、消息发送和消息处理三个部分分析完Android应用程序的消息处理机制了，为了更深理解，这里我们对其中的一些要点作一个总结：<br /></p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; A. Android应用程序的消息处理机制由消息循环、消息发送和消息处理三个部分组成的。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; B. Android应用程序的主线程在进入消息循环过程前，会在内部创建一个Linux管道（Pipe），这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态，并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; C. Android应用程序的主线程进入空闲等待状态的方式实际上就是在管道的读端等待管道中有新的内容可读，具体来说就是是通过Linux系统的Epoll机制中的epoll_wait函数进行的。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; D. 当往Android应用程序的消息队列中加入新的消息时，会同时往管道中的写端写入内容，通过这种方式就可以唤醒正在等待消息到来的应用程序主线程。</p><p align="left">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; E. 当应用程序主线程在进入空闲等待前，会认为当前线程处理空闲状态，于是就会调用那些已经注册了的IdleHandler接口，使得应用程序有机会在空闲的时候处理一些事情。</p><p align="left" style="text-align: center;"><span style="font-size:14px;color:#cc0000;"><strong>老罗的新浪微博：<a target=_blank target="_blank" href="http://weibo.com/shengyangluo">http://weibo.com/shengyangluo</a>，欢迎关注！</strong></span><br /></p>
</div>



<!-- Baidu Button BEGIN -->
<div id="bdshare" class="bdshare_t bds_tools get-codes-bdshare" style="float: right;">
<a class="bds_qzone"></a>
<a class="bds_tsina"></a>
<a class="bds_tqq"></a>
<a class="bds_renren"></a>
<a class="bds_t163"></a>
<span class="bds_more">更多</span>
<a class="shareCount"></a>
</div>
<!-- Baidu Button END -->


<!--192.168.100.34-->
<div class="article_next_prev">
            <li class="prev_article"><span>上一篇：</span><a href="/luoshengyang/article/details/6786239" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_shangyipian'])">Android系统的智能指针（轻量级指针、强指针和弱指针）的实现原理分析</a></li>
            <li class="next_article"><span>下一篇：</span><a href="/luoshengyang/article/details/6882903" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_xiayipian'])">Android应用程序键盘（Keyboard）消息处理机制分析</a></li>
</div>

<!-- Baidu Button BEGIN -->
<script type="text/javascript" id="bdshare_js" data="type=tools&amp;uid=1536434" ></script>
<script type="text/javascript" id="bdshell_js"></script>
<script type="text/javascript">
    document.getElementById("bdshell_js").src = "http://bdimg.share.baidu.com/static/js/shell_v2.js?cdnversion=" + Math.ceil(new Date()/3600000)
</script>
<!-- Baidu Button END -->

        <div id="digg" ArticleId="6817933">
            <dl id="btnDigg" class="digg digg_disable">
                <dt onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_ding'])">顶</dt>
                <dd>39</dd>
            </dl>
            <dl id="btnBury" class="digg digg_disable">
                <dt onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_cai'])">踩</dt>
                <dd>1</dd>
            </dl>
        </div>
</div>
      <dl class="blog-associat-tag">
        <dt>相关主题推荐</dt>
        <dd>
                <a href="http://www.csdn.net/tag/android应用" target="_blank">android应用</a> 
                <a href="http://www.csdn.net/tag/linux内核" target="_blank">linux内核</a> 
                <a href="http://www.csdn.net/tag/新浪微博" target="_blank">新浪微博</a> 
                <a href="http://www.csdn.net/tag/局部变量" target="_blank">局部变量</a> 
                <a href="http://www.csdn.net/tag/application" target="_blank">application</a> 
        </dd>
    </dl> 
    <dl class="blog-ass-articl">
        <dt>相关博文推荐</dt>
            <dd>
                <a href="http://blog.csdn.net/liao0000/article/details/21105875" title="一个android汽车试验app的技术memo" target="_blank">一个android汽车试验app的技术m...</a></dd>
            <dd>
                <a href="http://blog.csdn.net/ydxmemory/article/details/21089431" title="让你的网站学会跳舞！" target="_blank">让你的网站学会跳舞！</a></dd>
            <dd>
                <a href="http://blog.csdn.net/tianshi_1105/article/details/21089007" title="云取证：云网络取证数据的采集" target="_blank">云取证：云网络取证数据的采集</a></dd>
            <dd>
                <a href="http://blog.csdn.net/u013010416/article/details/21085539" title="【※】非限定宽度横向滚动应用实例(DIV + CSS、鼠标拖动)" target="_blank">【※】非限定宽度横向滚动应用实例(DIV...</a></dd>
            <dd>
                <a href="http://blog.csdn.net/u013010416/article/details/21085355" title="文字滚动应用，禁止鼠标控制暂停或滚动" target="_blank">文字滚动应用，禁止鼠标控制暂停或滚动</a></dd>
            <dd>
                <a href="http://blog.csdn.net/u012503608/article/details/21084841" title="状 态图法" target="_blank">状 态图法</a></dd>
            <dd>
                <a href="http://blog.csdn.net/liao0000/article/details/21078943" title="android周边游app的技术memo" target="_blank">android周边游app的技术memo</a></dd>
            <dd>
                <a href="http://blog.csdn.net/wlphlj/article/details/21081015" title="Linux命令组合应用" target="_blank">Linux命令组合应用</a></dd>
    </dl>

    <div id="ad_cen">
        <script type="text/javascript">            BAIDU_CLB_SLOT_ID = "117306";</script>
        <script type="text/javascript" src="http://cbjs.baidu.com/js/o.js"></script>
    </div>
    <script type="text/javascript">
        //new Ad(4, 'ad_cen');
    </script>
<div id="comment_title" class="panel_head">
    查看评论<a name="comments"></a></div>
<div id="comment_list">
</div>
<div id="comment_bar">
</div>
<div id="comment_form">
</div>
<div class="announce">
    * 以上用户言论只代表其个人观点，不代表CSDN网站的观点或立场<a name="reply"></a><a name="quote"></a></div>
<script type="text/javascript">
    var fileName = '6817933';
    var commentscount = 84;
    var islock = false
</script>
<script type="text/javascript" src="http://static.blog.csdn.net/scripts/comment.js"></script>
    <div id="ad_bot">
    </div>
    <script type="text/javascript">
    new Ad(5, 'ad_bot');
    </script>
<div id="report_dialog">
</div>
<div id="d-top" style="display: none;">
    <a id="d-top-a" href="#" title="回到顶部" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_huidaodingbu'])">
        <img src="http://static.blog.csdn.net/images/top.png" alt="TOP" /></a>
</div>
<script type="text/javascript">
    $(function ()
    {
        var d_top = $('#d-top');
        document.onscroll = function ()
        {
            var scrTop = (document.body.scrollTop || document.documentElement.scrollTop);
            if (scrTop > 500)
            {
                d_top.show();
            } else
            {
                d_top.hide();
            }
        }
        $('#d-top-a').click(function ()
        {
            scrollTo(0, 0);
            this.blur();
            return false;
        });
    });
  
</script>
<style type="text/css">
    .tag_list
    {
        background: none repeat scroll 0 0 #FFFFFF;
        border: 1px solid #D7CBC1;
        color: #000000;
        font-size: 12px;
        line-height: 20px;
        list-style: none outside none;
        margin: 10px 2% 0 1%;
        padding: 1px;
    }
    .tag_list h5
    {
        background: none repeat scroll 0 0 #E0DBD3;
        color: #47381C;
        font-size: 12px;
        height: 24px;
        line-height: 24px;
        padding: 0 5px;
        margin: 0;
    }
    .tag_list h5 a
    {
        color: #47381C;
    }
    .classify
    {
        margin: 10px 0;
        padding: 4px 12px 8px;
    }
    .classify a
    {
        margin-right: 20px;
        white-space: nowrap;
    }
</style>
<div class="tag_list">
    <h5>
        <a href="http://www.csdn.net/tag/" target="_blank">核心技术类目</a></h5>
    <div class="classify">
<a title="全部主题" href="http://www.csdn.net/tag" target="_blank" onclick="LogClickCount(this,336);">全部主题</a>
<a title="Java" href="http://www.csdn.net/tag/Java" target="_blank" onclick="LogClickCount(this,336);">Java</a>
<a title="VPN" href="http://www.csdn.net/tag/vpn" target="_blank" onclick="LogClickCount(this,336);">VPN</a>
<a title="Android" href="http://www.csdn.net/tag/android" target="_blank" onclick="LogClickCount(this,336);">Android</a>
<a title="iOS" href="http://www.csdn.net/tag/ios" target="_blank" onclick="LogClickCount(this,336);">iOS</a>
<a title="ERP" href="http://www.csdn.net/tag/erp" target="_blank" onclick="LogClickCount(this,336);">ERP</a>
<a title="IE10" href="http://www.csdn.net/tag/ie10" target="_blank" onclick="LogClickCount(this,336);">IE10</a>
<a title="Eclipse" href="http://www.csdn.net/tag/eclipse" target="_blank" onclick="LogClickCount(this,336);">Eclipse</a>
<a title="CRM" href="http://www.csdn.net/tag/crm" target="_blank" onclick="LogClickCount(this,336);">CRM</a>
<a title="JavaScript" href="http://www.csdn.net/tag/javascript" target="_blank" onclick="LogClickCount(this,336);">JavaScript</a>
<a title="Ubuntu" href="http://www.csdn.net/tag/ubuntu" target="_blank" onclick="LogClickCount(this,336);">Ubuntu</a>
<a title="NFC" href="http://www.csdn.net/tag/nfc" target="_blank" onclick="LogClickCount(this,336);">NFC</a>
<a title="WAP" href="http://www.csdn.net/tag/wap" target="_blank" onclick="LogClickCount(this,336);">WAP</a>
<a title="jQuery" href="http://www.csdn.net/tag/jquery" target="_blank" onclick="LogClickCount(this,336);">jQuery</a>
<a title="数据库" href="http://www.csdn.net/tag/数据库" target="_blank" onclick="LogClickCount(this,336);">数据库</a>
<a title="BI" href="http://www.csdn.net/tag/bi" target="_blank" onclick="LogClickCount(this,336);">BI</a>
<a title="HTML5" href="http://www.csdn.net/tag/html5" target="_blank" onclick="LogClickCount(this,336);">HTML5</a>
<a title="Spring" href="http://www.csdn.net/tag/spring" target="_blank" onclick="LogClickCount(this,336);">Spring</a>
<a title="Apache" href="http://www.csdn.net/tag/apache" target="_blank" onclick="LogClickCount(this,336);">Apache</a>
<a title="Hadoop" href="http://www.csdn.net/tag/hadoop" target="_blank" onclick="LogClickCount(this,336);">Hadoop</a>
<a title=".NET" href="http://www.csdn.net/tag/.net" target="_blank" onclick="LogClickCount(this,336);">.NET</a>
<a title="API" href="http://www.csdn.net/tag/api" target="_blank" onclick="LogClickCount(this,336);">API</a>
<a title="HTML" href="http://www.csdn.net/tag/html" target="_blank" onclick="LogClickCount(this,336);">HTML</a>
<a title="SDK" href="http://www.csdn.net/tag/sdk" target="_blank" onclick="LogClickCount(this,336);">SDK</a>
<a title="IIS" href="http://www.csdn.net/tag/iis" target="_blank" onclick="LogClickCount(this,336);">IIS</a>
<a title="Fedora" href="http://www.csdn.net/tag/fedora" target="_blank" onclick="LogClickCount(this,336);">Fedora</a>
<a title="XML" href="http://www.csdn.net/tag/xml" target="_blank" onclick="LogClickCount(this,336);">XML</a>
<a title="LBS" href="http://www.csdn.net/tag/lbs" target="_blank" onclick="LogClickCount(this,336);">LBS</a>
<a title="Unity" href="http://www.csdn.net/tag/unity" target="_blank" onclick="LogClickCount(this,336);">Unity</a>
<a title="Splashtop" href="http://www.csdn.net/tag/splashtop" target="_blank" onclick="LogClickCount(this,336);">Splashtop</a>
<a title="UML" href="http://www.csdn.net/tag/uml" target="_blank" onclick="LogClickCount(this,336);">UML</a>
<a title="components" href="http://www.csdn.net/tag/components" target="_blank" onclick="LogClickCount(this,336);">components</a>
<a title="Windows Mobile" href="http://www.csdn.net/tag/windowsmobile" target="_blank" onclick="LogClickCount(this,336);">Windows Mobile</a>
<a title="Rails" href="http://www.csdn.net/tag/rails" target="_blank" onclick="LogClickCount(this,336);">Rails</a>
<a title="QEMU" href="http://www.csdn.net/tag/qemu" target="_blank" onclick="LogClickCount(this,336);">QEMU</a>
<a title="KDE" href="http://www.csdn.net/tag/kde" target="_blank" onclick="LogClickCount(this,336);">KDE</a>
<a title="Cassandra" href="http://www.csdn.net/tag/cassandra" target="_blank" onclick="LogClickCount(this,336);">Cassandra</a>
<a title="CloudStack" href="http://www.csdn.net/tag/cloudstack" target="_blank" onclick="LogClickCount(this,336);">CloudStack</a>
<a title="FTC" href="http://www.csdn.net/tag/ftc" target="_blank" onclick="LogClickCount(this,336);">FTC</a>
<a title="coremail" href="http://www.csdn.net/tag/coremail" target="_blank" onclick="LogClickCount(this,336);">coremail</a>
<a title="OPhone " href="http://www.csdn.net/tag/ophone " target="_blank" onclick="LogClickCount(this,336);">OPhone </a>
<a title="CouchBase" href="http://www.csdn.net/tag/couchbase" target="_blank" onclick="LogClickCount(this,336);">CouchBase</a>
<a title="云计算" href="http://www.csdn.net/tag/云计算" target="_blank" onclick="LogClickCount(this,336);">云计算</a>
<a title="iOS6" href="http://www.csdn.net/tag/iOS6" target="_blank" onclick="LogClickCount(this,336);">iOS6</a>
<a title="Rackspace " href="http://www.csdn.net/tag/rackspace " target="_blank" onclick="LogClickCount(this,336);">Rackspace </a>
<a title="Web App" href="http://www.csdn.net/tag/webapp" target="_blank" onclick="LogClickCount(this,336);">Web App</a>
<a title="SpringSide" href="http://www.csdn.net/tag/springside" target="_blank" onclick="LogClickCount(this,336);">SpringSide</a>
<a title="Maemo" href="http://www.csdn.net/tag/maemo" target="_blank" onclick="LogClickCount(this,336);">Maemo</a>
<a title="Compuware" href="http://www.csdn.net/tag/compuware" target="_blank" onclick="LogClickCount(this,336);">Compuware</a>
<a title="大数据" href="http://www.csdn.net/tag/大数据" target="_blank" onclick="LogClickCount(this,336);">大数据</a>
<a title="aptech" href="http://www.csdn.net/tag/aptech" target="_blank" onclick="LogClickCount(this,336);">aptech</a>
<a title="Perl" href="http://www.csdn.net/tag/perl" target="_blank" onclick="LogClickCount(this,336);">Perl</a>
<a title="Tornado" href="http://www.csdn.net/tag/tornado" target="_blank" onclick="LogClickCount(this,336);">Tornado</a>
<a title="Ruby" href="http://www.csdn.net/tag/ruby" target="_blank" onclick="LogClickCount(this,336);">Ruby</a>
<a title="Hibernate" href="http://www.csdn.net/hibernate" target="_blank" onclick="LogClickCount(this,336);">Hibernate</a>
<a title="ThinkPHP" href="http://www.csdn.net/tag/thinkphp" target="_blank" onclick="LogClickCount(this,336);">ThinkPHP</a>
<a title="Spark" href="http://www.csdn.net/tag/spark" target="_blank" onclick="LogClickCount(this,336);">Spark</a>
<a title="HBase" href="http://www.csdn.net/tag/hbase" target="_blank" onclick="LogClickCount(this,336);">HBase</a>
<a title="Pure" href="http://www.csdn.net/tag/pure" target="_blank" onclick="LogClickCount(this,336);">Pure</a>
<a title="Solr" href="http://www.csdn.net/tag/solr" target="_blank" onclick="LogClickCount(this,336);">Solr</a>
<a title="Angular" href="http://www.csdn.net/tag/angular" target="_blank" onclick="LogClickCount(this,336);">Angular</a>
<a title="Cloud Foundry" href="http://www.csdn.net/tag/cloudfoundry" target="_blank" onclick="LogClickCount(this,336);">Cloud Foundry</a>
<a title="Redis" href="http://www.csdn.net/tag/redis" target="_blank" onclick="LogClickCount(this,336);">Redis</a>
<a title="Scala" href="http://www.csdn.net/tag/scala" target="_blank" onclick="LogClickCount(this,336);">Scala</a>
<a title="Django" href="http://www.csdn.net/tag/django" target="_blank" onclick="LogClickCount(this,336);">Django</a>
<a title="Bootstrap" href="http://www.csdn.net/tag/bootstrap" target="_blank" onclick="LogClickCount(this,336);">Bootstrap</a>
    </div>
</div>

                    <div class="clear">
                    </div>
                </div>
            </div>
            <div id="side">
    <div class="side">
<div id="panel_Profile" class="panel">
<ul class="panel_head"><span>个人资料</span></ul>
<ul class="panel_body profile">
<div id="blog_userface">
    <a href="http://my.csdn.net/Luoshengyang" target="_blank">
    <img src="http://avatar.csdn.net/5/6/E/1_luoshengyang.jpg" title="访问我的空间" style="max-width:90%"/>
    </a>
    <br />
    <span><a href="http://my.csdn.net/Luoshengyang" class="user_name" target="_blank">Luoshengyang</a></span>
</div>
<div class="interact">
<a href="javascript:void(0);" class="attent" id="span_add_follow" title="[加关注]" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_guanzhu'])"></a>
<a href="javascript:void(0);" class="letter" onclick="loginto(1)" title="[发私信]" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_sixin'])"></a>
</div>
<div id="blog_medal">
        <div class="ico_expert" title="CSDN认证专家"></div>
</div>
<ul id="blog_rank">
    <li>访问：<span>3159552次</span></li>
    <li>积分：<span>16447分</span></li>
    <li>排名：<span>第129名</span></li>
</ul>
<ul id="blog_statistics">
    <li>原创：<span>100篇</span></li>
    <li>转载：<span>0篇</span></li>
    <li>译文：<span>0篇</span></li>
    <li>评论：<span>5212条</span></li>
</ul>
</ul>
</div>


<div id="custom_column_6567056" class="panel">
<ul class="panel_head"><span>博客公告</span></ul>
<ul class="panel_body">
<b><font color="red" size="2">本博客所有文章均为原创，欢迎交流，欢迎转载；转载请勿篡改内容，并且注明出处，禁止用于商业目的，谢谢！</font></b>
</ul>
</div><div id="custom_column_21664874" class="panel">
<ul class="panel_head"><span>图书信息</span></ul>
<ul class="panel_body">
<font color="red"><b>书名：</b></font>
<p>《Android系统源代码情景分析》</p>

<p><a href="http://wenku.it168.com/d_000574719.shtml" target="_blank">试读请点击</a></p>

<p><font color="red"><b>出版社：</b></font></p>
<p>电子工业出版社</p>

<p><font color="red"><b>网店：</b></font></p>

<p>1. <a href="http://product.dangdang.com/product.aspx?product_id=22884086" target="_blank">当当网（满100减20，满200减50）</a></p><p></p>

<p>2. <a href="http://book.360buy.com/11104739.html" target="_blank">京东网（满99减20）</a></p><p></p>

<p>3. <a href="http://www.amazon.cn/Android%E7%B3%BB%E7%BB%9F%E6%BA%90%E4%BB%A3%E7%A0%81%E6%83%85%E6%99%AF%E5%88%86%E6%9E%90-%E7%BD%97%E5%8D%87%E9%98%B3/dp/B009OLU8EE" target="_blank">亚马逊网（满100减20，满200减50）</a></p><p></p>

<p>4. <a href="http://product.china-pub.com/3684413" target="_blank">互动出版网</a></p><p></p>

<p>5. <a href="http://www.suning.com/emall/prd_10052_22001_-7_3435777_.html" target="_blank">苏宁易购网</a></p><p></p>

<p><a href="http://s.etao.com/search?spm=1002.8.1.1386.g76Rk3&amp;q=Android%CF%B5%CD%B3%D4%B4%B4%FA%C2%EB%C7%E9%BE%B0%B7%D6%CE%F6&amp;style=list&amp;initiative_id=etao_20121011&amp;pn=1&amp;sort=sale-desc" target="_blank">比一比谁更实惠</a></p>

<p><font color="red"><b>号外：</b></font></p>

<p>本书繁体版已经成功输出到台湾</p>

<p></p>
</ul>
</div><div id="custom_column_21603386" class="panel">
<ul class="panel_head"><span>联系方式</span></ul>
<ul class="panel_body">
<font color="red"><b>新浪微博：</b></font>
<p><a href="http://weibo.com/shengyangluo" target="_blank"><font color="red"><b>http://weibo.com/shengyangluo</b></font></a></p><p></p>

<font color="red"><b>QQ交流群：</b></font>
<p><b><font color="red">130112760</font></b>（1000人群，已满）</p>
<p><b><font color="red">248749286</font></b>（1000人群，已满）</p>
<p><b><font color="red">204155322</font></b>（500人群，未满）</p>
<p><b><font color="red">278558417</font></b>（500人群，已满）</p>
<p><b><font color="red">303165655</font></b>（500人群，已满）</p>
<p>PS：请勿同时加入多个群，一经发现，永久封号，谢谢！</p>
</ul>
</div><div id="panel_Category" class="panel">
    <ul class="panel_head"><span>博客专栏</span></ul>
    <ul class="panel_body" id="sp_column">
    <table cellpadding="0" cellspacing="0"><tr>
    <td style="padding:10px 10px 0 0;">
    <a href="http://blog.csdn.net/column/details/androidluo.html" target="_blank"><img src="http://avatar.csdn.net/blogpic/20111107095438958.jpg" style="width:75px;height:75px;" /></a>
    </td>
    <td style="padding:10px 0; vertical-align:top;">
    <a href="http://blog.csdn.net/column/details/androidluo.html" target="_blank">老罗的Android之旅</a>
    <p>文章：100篇</p>
    <span>阅读：3163373</span>
    </td>
    </tr></table>
    </ul>
</div><div id="hotarticls" class="panel">
<ul class="panel_head"><span>阅读排行</span></ul>
<ul class="panel_body itemlist">
<li>
<a href="/luoshengyang/article/details/6559955" title="在Ubuntu上下载、编译和安装Android最新源代码">在Ubuntu上下载、编译和安装Android最新源代码</a><span>(109317)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6618363" title="Android进程间通信（IPC）机制Binder简要介绍和学习计划">Android进程间通信（IPC）机制Binder简要介绍和学习计划</a><span>(95185)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6689748" title="Android应用程序启动过程源代码分析">Android应用程序启动过程源代码分析</a><span>(80750)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6568411" title="在Ubuntu上为Android系统编写Linux内核驱动程序">在Ubuntu上为Android系统编写Linux内核驱动程序</a><span>(78561)</span>
</li>
<li>
<a href="/luoshengyang/article/details/8923485" title="那两年炼就的Android内功修养">那两年炼就的Android内功修养</a><span>(76460)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6567257" title="Android硬件抽象层（HAL）概要介绍和学习计划">Android硬件抽象层（HAL）概要介绍和学习计划</a><span>(72469)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6564592" title="在Ubuntu上下载、编译和安装Android最新内核源代码（Linux Kernel）">在Ubuntu上下载、编译和安装Android最新内核源代码（Linux Kernel）</a><span>(66668)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6557518" title="Android学习启动篇">Android学习启动篇</a><span>(58980)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6566662" title="如何单独编译Android源代码中的模块">如何单独编译Android源代码中的模块</a><span>(57960)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6575988" title="在Ubuntu为Android硬件抽象层（HAL）模块编写JNI方法提供Java访问硬件服务接口">在Ubuntu为Android硬件抽象层（HAL）模块编写JNI方法提供Java访问硬件服务接口</a><span>(56692)</span>
</li>
</ul>
</div>
<div id="hotarticls2" class="panel">
<ul class="panel_head"><span>评论排行</span></ul>
<ul class="panel_body itemlist">
<li>
<a href="/luoshengyang/article/details/6689748" title="Android应用程序启动过程源代码分析">Android应用程序启动过程源代码分析</a><span>(322)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6559955" title="在Ubuntu上下载、编译和安装Android最新源代码">在Ubuntu上下载、编译和安装Android最新源代码</a><span>(238)</span>
</li>
<li>
<a href="/luoshengyang/article/details/8116866" title="《Android系统源代码情景分析》一书勘误">《Android系统源代码情景分析》一书勘误</a><span>(208)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6882903" title="Android应用程序键盘（Keyboard）消息处理机制分析">Android应用程序键盘（Keyboard）消息处理机制分析</a><span>(182)</span>
</li>
<li>
<a href="/luoshengyang/article/details/8923485" title="那两年炼就的Android内功修养">那两年炼就的Android内功修养</a><span>(181)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6580267" title="在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务">在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务</a><span>(170)</span>
</li>
<li>
<a href="/luoshengyang/article/details/7409491" title="Android博客文章整理">Android博客文章整理</a><span>(167)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6568411" title="在Ubuntu上为Android系统编写Linux内核驱动程序">在Ubuntu上为Android系统编写Linux内核驱动程序</a><span>(151)</span>
</li>
<li>
<a href="/luoshengyang/article/details/8452527" title="2012年的Android之旅：梦想、学习、坚持、自信、淡定">2012年的Android之旅：梦想、学习、坚持、自信、淡定</a><span>(139)</span>
</li>
<li>
<a href="/luoshengyang/article/details/6564592" title="在Ubuntu上下载、编译和安装Android最新内核源代码（Linux Kernel）">在Ubuntu上下载、编译和安装Android最新内核源代码（Linux Kernel）</a><span>(136)</span>
</li>
</ul>
</div>
<div id="newcomments" class="panel">
<ul class="panel_head"><span>最新评论</span></ul>
<ul class="panel_body itemlist">
    <li>
    <a href="/Luoshengyang/article/details/6568411#comments">在Ubuntu上为Android系统编写Linux内核驱动程序</a>
    <p style="margin:0px;"><a href="/HuiyuYang_fish" class="user_name">HuiyuYang_fish</a>:
期待实验~
    </p>
    </li>
    <li>
    <a href="/Luoshengyang/article/details/6557518#comments">Android学习启动篇</a>
    <p style="margin:0px;"><a href="/HuiyuYang_fish" class="user_name">HuiyuYang_fish</a>:
貌似我也要开始android的初步涉猎了~
    </p>
    </li>
    <li>
    <a href="/Luoshengyang/article/details/12957169#comments">《老罗的Android之旅》导读PPT</a>
    <p style="margin:0px;"><a href="/mianju23" class="user_name">mianju23</a>:
牛人
    </p>
    </li>
    <li>
    <a href="/Luoshengyang/article/details/8452527#comments">2012年的Android之旅：梦想、学习、坚持、自信、淡定</a>
    <p style="margin:0px;"><a href="/wuxiaoming1733" class="user_name">wuxiaoming1733</a>:
顶楼主
    </p>
    </li>
    <li>
    <a href="/Luoshengyang/article/details/6737352#comments">Android应用程序注册广播接收器（registerReceiver）的过程分析</a>
    <p style="margin:0px;"><a href="/wuxiaoming1733" class="user_name">wuxiaoming1733</a>:
罗大神你好，我这边还没明白，怎么自己用代码，实现androidmanifest.xml里静态注册系统...
    </p>
    </li>
    <li>
    <a href="/Luoshengyang/article/details/6642463#comments">Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析</a>
    <p style="margin:0px;"><a href="/zqz19891031" class="user_name">zqz19891031</a>:
博主您好，看了你的博客，收获颇多！有一个问题想请教：最近在看Android的Binder实现，看到B...
    </p>
    </li>
    <li>
    <a href="/Luoshengyang/article/details/8923485#comments">那两年炼就的Android内功修养</a>
    <p style="margin:0px;"><a href="/xin128888" class="user_name">xin128888</a>:
老罗，有问题能否指导下。目前我想学习驱动相关知识，但是不知道要看哪本入门书籍合适，目标主要是快速入门...
    </p>
    </li>
    <li>
    <a href="/Luoshengyang/article/details/19023609#comments">Android源代码编译命令m/mm/mmm/make分析</a>
    <p style="margin:0px;"><a href="/soledadzz" class="user_name">soledadzz</a>:
您的文章已被推荐到CSDN首页，感谢您的分享。
    </p>
    </li>
    <li>
    <a href="/Luoshengyang/article/details/19023609#comments">Android源代码编译命令m/mm/mmm/make分析</a>
    <p style="margin:0px;"><a href="/u013725065" class="user_name">u013725065</a>:

    </p>
    </li>
    <li>
    <a href="/Luoshengyang/article/details/19023609#comments">Android源代码编译命令m/mm/mmm/make分析</a>
    <p style="margin:0px;"><a href="/u013725065" class="user_name">u013725065</a>:

    </p>
    </li>
</ul>
</div>
    </div>
    <div class="clear">
    </div>
</div>

            <div class="clear">
            </div>
        </div>
        

<script type="text/javascript" src="http://static.blog.csdn.net/scripts/newblog.min.js"></script>
<script type="text/javascript" src="http://medal.blog.csdn.net/showblogmedal.ashx?blogid=1134701"></script>
<script type="text/javascript">
    document.write('<script type="text/javascript" src="http://csdnimg.cn/pubfooter/js/publib_footer.js?' + Math.floor(new Date() / 120000).toString(36) + '="></' + 'script>');
</script>
    <script type="text/javascript" src="http://passport.csdn.net/content/loginbox/login.js"></script>
<script type="text/javascript">document.write("<img src=http://counter.csdn.net/pv.aspx?id=24 border=0 width=0 height=0>");</script>
<script type="text/javascript" src="http://www.csdn.net/ui/scripts/Csdn/counter.js"></script>
<script type="text/javascript" src="http://ad.csdn.net/scripts/ad-blog.js"></script>
<script type="text/javascript" src="http://zz.csdn.net/js/count.js"></script>
<script type="text/javascript">
    $(function () {
        function __get_code_toolbar(snippet_id) {
            return $("<a href='https://code.csdn.net/snippets/"
                    + snippet_id
                    + "' target='_blank' title='在CODE上查看代码片' style='text-indent:0;'><img src='https://code.csdn.net/assets/CODE_ico.png' width=12 height=12 alt='在CODE上查看代码片' style='position:relative;top:1px;left:2px;'/></a>"
                    + "<a href='https://code.csdn.net/snippets/"
                    + snippet_id
                    + "/fork' target='_blank' title='派生到我的代码片'  style='text-indent:0;'><img src='https://code.csdn.net/assets/ico_fork.svg' width=12 height=12 alt='派生到我的代码片' style='position:relative;top:2px;left:2px;'/></a>");
        }
        
        $("[code_snippet_id]").each(function () {
            __s_id = $(this).attr("code_snippet_id");
            if (__s_id != null && __s_id != "" && __s_id != 0 && parseInt(__s_id) > 70020) {
                __code_tool = __get_code_toolbar(__s_id);
                $(this).prev().find(".tools").append(__code_tool);
            }
        });
    });
</script>

    </div>
      <!--new top-->
    
    <script id="csdn-toolbar-id" btnId="header_notice_num" wrapId="note1" count="5" subCount="5" type="text/javascript" src="http://static.csdn.net/public/common/toolbar/js/toolbar.js"></script>     <!--new top-->
</body>
</html>
